﻿using System;
using System.Collections.Generic;
using WinAPI;
namespace DriveManagement
{
    abstract class DriveManager
    {
        #region  exception classes
        public class LetterIsBusyException : Exception
        {
            public LetterIsBusyException(char letter) : base(GUI.Resources.Strings.Volume + letter + GUI.Resources.Strings.IsBusy) { }
        }
        public class MountErrorException : Exception
        {
            public MountErrorException(int code) : base(GUI.Resources.Strings.SomeError + code) { }
        }
        #endregion

        private const string deviceName = @"\Device\HardDisk\VirtualDisk_";

        private const string masterDeviceName = @"\\.\MasterVirtualDisk";

        private static List<char> freeLetters = new List<char>(26);//reserving

        private static List<DriveInfo> virtualDrives = new List<DriveInfo>();

        private static Dictionary<char, int> letters = new Dictionary<char, int>(26);//reserving

        static DriveManager()
        {
            int i = 0;
            for (char ch = 'A'; ch <= 'Z'; ++ch)
                letters.Add(ch, ++i);
        }

        private static List<char> GetFreeDrivesList()
        {
            freeLetters.Clear();
            System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives();
            for (char ch = 'A'; ch <= 'Z'; ++ch)
                freeLetters.Add(ch);
            foreach (System.IO.DriveInfo drive in drives)
                freeLetters.Remove(drive.Name[0]);
            foreach (DriveInfo item in virtualDrives)
            {
                freeLetters.Remove(item.Letter);
            }
            return freeLetters;
        }

        public static List<char> FreeLetters
        {//for binding with ComboBox
            get
            {
                return GetFreeDrivesList();
            }
        }

        private static bool IsLetterFree(char letter)
        {
            System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives();
            foreach (System.IO.DriveInfo item in drives)
            {
                if (item.Name[0] == letter)
                    return false;
            }
            return true;
        }

        public static void CreateFile(string fileName, Int64 size)
        {//create new file and set it size

            try
            {
                using (System.IO.FileStream fStream = new System.IO.FileStream(fileName, System.IO.FileMode.Create))
                {
                    fStream.SetLength(size);//file growing to necessary size
                }
            }
            catch (Exception)
            {
                System.IO.FileInfo f = new System.IO.FileInfo(fileName);
                f.Delete(); //delete created file
                throw;
            }
        }

        public static DriveInfo Mount(string fileName, Int64 size, char letter, string password)
        {
            if (password == string.Empty)
                password = " ";
            if (!DriveManager.IsLetterFree(letter))
                throw new LetterIsBusyException(letter);

            if (isFileMounted(fileName))
                throw new System.IO.FileLoadException(GUI.Resources.Strings.AlreadyMnt, fileName);

            DriveInfo drive = new DriveInfo(fileName, letter, size, deviceName + letters[letter]);
            try
            {
                using (HandleGuard hMasterDevice = LibWrapper.OpenMasterDevice(masterDeviceName))
                {
                    LibWrapper.CreateDevice(hMasterDevice, letters[letter]);
                    LibWrapper.DefineDevice(letter, deviceName + letters[letter].ToString());
                    //mounting file as drive 
                    using (HandleGuard device = LibWrapper.OpenVolume(letter))
                    {
                        //file checking and mounting 
                        LibWrapper.DeviceIoControl_FileOpen(device, fileName, size, letter, password);
                        LibWrapper.SHChangeNotifyAddDrive(letter);
                    }
                    //make info struct
                    System.IO.DriveInfo volume = new System.IO.DriveInfo(letter.ToString());
                    try
                    {       //exception will be throwed if drive is not contains any filesystem
                        volume.VolumeLabel = drive.FileNameWithoutExtension;
                    }
                    catch (Exception) { }//just can`t set volume label
                    drive.Mounted = true;
                    virtualDrives.Add(drive);
                    return drive;
                }
            }
            catch (Exception ex)
            {//mounting error
                try
                {
                    using (HandleGuard volume = LibWrapper.OpenVolume(drive.Letter))
                    {
                        LibWrapper.DeviceIoControl(volume, LibWrapper.CTL_CODE(LibWrapper.MULTI_VDD_DEVICE, 0x803, LibWrapper.METHOD_BUFFERED, LibWrapper.FILE_READ_ACCESS | LibWrapper.FILE_WRITE_ACCESS));
                    }
                    using (HandleGuard device = LibWrapper.OpenVolume(letter))
                    {
                        LibWrapper.DeviceIoControl_REMOVE(device, letters[letter]);
                    }
                    LibWrapper.UnDefineDevice(letter);
                    LibWrapper.DeleteVolumeMountPoint(letter.ToString());
                }
                catch (Exception) { }//errors handling while errors handling...xzibit, pimp my ride!
                finally
                {
                    drive.Mounted = false;   //needs to rollback drives info
                    virtualDrives.Remove(drive);
                    if (ex is MarshalException)
                        throw new MountErrorException((ex as MarshalException).ErrorCode);//including Error code
                    else
                        throw ex;
                }
            }
        }

        public static bool isFileMounted(string name)
        {
            foreach (DriveInfo drive in virtualDrives)
            {
                if (drive.FileName.Equals(name) && drive.Mounted)
                    return true;
            }
            return false;
        }

        public static void Unmount(DriveInfo drive)
        {
            using (HandleGuard volume = LibWrapper.OpenVolume(drive.Letter))
            {
                //lock
                LibWrapper.DeviceIoControl(volume, LibWrapper.CTL_CODE(LibWrapper.FILE_DEVICE_FILE_SYSTEM, LibWrapper.FSCTL_LOCK_VOLUME, LibWrapper.METHOD_BUFFERED, LibWrapper.FILE_ANY_ACCESS));
                //close file
                LibWrapper.DeviceIoControl(volume, LibWrapper.CTL_CODE(LibWrapper.MULTI_VDD_DEVICE, 0x803, LibWrapper.METHOD_BUFFERED, LibWrapper.FILE_READ_ACCESS | LibWrapper.FILE_WRITE_ACCESS));
                //dismount volume
                LibWrapper.DeviceIoControl(volume, LibWrapper.CTL_CODE(LibWrapper.FILE_DEVICE_FILE_SYSTEM, LibWrapper.FSCTL_DISMOUNT_VOLUME, LibWrapper.METHOD_BUFFERED, LibWrapper.FILE_ANY_ACCESS));
            }
            //remove dos device definition 
            LibWrapper.UnDefineDevice(drive.Letter);
            LibWrapper.SHChangeNotifyDeleteDrive(drive.Letter);
            using (HandleGuard slaveDevice = LibWrapper.OpenMasterDevice(masterDeviceName))
            {
                LibWrapper.DeviceIoControl_REMOVE(slaveDevice, letters[drive.Letter]);
            }
            drive.Mounted = false;
            RemoveDrive(drive);
        }
        public static void RemoveDrive(DriveInfo info)
        {
            virtualDrives.Remove(info);
        }

        public static void AddDrive(DriveInfo drive)
        {
            virtualDrives.Add(drive);
        }
    }
}